/*
 * MemoryByteChannelImage
 *
 * Copyright (c) 2002, 2003 Marco Schmidt.
 * All rights reserved.
 */
using System;

namespace Petra.Gis.Geometry.Image.Tiff
{
  /**
   * An implementation of {@link IByteChannelImage} that stores image channels as
   * <code>byte[]</code> arrays in memory.
   * An image can have an arbitrary number of channels.
   * <p>
   * This class is abstract because it is merely a data container.
   * It takes a subclass like {@link MemoryGray8Image} to give meaning to the values.
   *
   * @author Marco Schmidt
   */
  public abstract class MemoryByteChannelImage : IByteChannelImage
  {
    private byte[][] data;
    private byte[] firstChannel; // == data[0]
    private int numChannels; // == data.length
    private int width;
    private int height;
    private int numPixels; // == width * height
    protected ImageFileDirectory _ifd;

    /**
     * Create an image of byte channels.
     * Image data will be completely in memory, so memory requirements are 
     * <code>width * height * numChannels</code> bytes.
     * Note that the data will not be initialized, so you should not assume
     * anything about its content.
     *
     * @param numChannels the number of channels in this image, must be
     *  non-zero and positive
     * @param width the horizontal resolution, must be non-zero and positive
     * @param height the vertical resolution, must be non-zero and positive
     * @throws ArgumentException if any of the parameters are invalid
     *  or if width times height exceeds two GB
     * @throws OutOfMemoryException if there is not enough free memory for
     *  the specified resolution
     */
    public MemoryByteChannelImage(ImageFileDirectory ifd, int numChannels, int width, int height)
    {
      if (width < 1)
      {
        throw new ArgumentException("Width must be larger than " +
          "0: " + width);
      }
      if (height < 1)
      {
        throw new ArgumentException("Height must be larger than" +
          " 0: " + height);
      }
      if (numChannels < 1)
      {
        throw new ArgumentException("Number of channels must be " +
          "larger than 0: " + numChannels);
      }
      this.width = width;
      this.height = height;
      this.numChannels = numChannels;
      numPixels = width * height;
      data = new byte[numChannels][];
      for (int i = 0; i < numChannels; i++)
      {
        data[i] = new byte[numPixels];
      }
      firstChannel = data[0];
      _ifd = ifd;
    }




    /**
     * Throws an exception if the arguments do not form a valid horizontal 
     * sequence of samples.
     * To be valid, all of the following requirements must be met:
     */
    protected void checkPositionAndNumber(RGBIndex channel, int x, int y, int w, int h)
    {
      if (channel < 0 || (int)channel >= numChannels)
      {
        throw new ArgumentException("Illegal channel index value: " + channel +
          ". Must be from 0 to " + (numChannels - 1) + ".");
      }
      if (x < 0 || x >= getWidth())
      {
        throw new ArgumentException("The value for x is invalid: " + x + ".");
      }
      if (w < 1)
      {
        throw new ArgumentException("The value for w is invalid: " + w + ".");
      }
      if (x + w > getWidth())
      {
        throw new ArgumentException("The values x + w exceed the " +
          "width of this image; x=" + x + ", w=" + w + ", width=" +
          getWidth());
      }
      if (h < 1)
      {
        throw new ArgumentException("The value for h is invalid: " + h + ".");
      }
      if (y < 0 || y >= getHeight())
      {
        throw new ArgumentException("The value for y is invalid: " + y + ".");
      }
      if (y + h > getHeight())
      {
        throw new ArgumentException("The values y + h exceed the " +
          "height of this image; y=" + y + ", h=" + h + ", height=" +
          getHeight());
      }
    }



    public void clear(byte newValue)
    {
      clear(0, newValue);
    }




    public void clear(RGBIndex channelIndex, byte newValue)
    {
      // check the validity of the channel index
      checkPositionAndNumber(channelIndex, 0, 0, 1, 1);
      // get the correct channel as byte[]
      byte[] CHANNEL = data[(int)channelIndex];
      // fill channel with the argument value
      byte VALUE = (byte)newValue;
      int LENGTH = CHANNEL.Length;
      for (int i = 0; i < LENGTH; i++)
      {
        CHANNEL[i] = VALUE;
      }
    }

    public void clear(int newValue)
    {
      clear(0, (byte)newValue);
    }



    public void clear(RGBIndex channelIndex, int newValue)
    {
      clear(channelIndex, (byte)newValue);
    }





    public abstract IPixelImage createCompatibleImage(int width, int height);






    public IPixelImage createCopy()
    {
      IPixelImage copy = createCompatibleImage(getWidth(), getHeight());
      MemoryByteChannelImage result = (MemoryByteChannelImage)copy;
      for (RGBIndex channelIndex = 0; (int)channelIndex < getNumChannels(); channelIndex++)
      {
        Array.Copy(data[(int)channelIndex], 0, result.data[(int)channelIndex], 0, data[(int)channelIndex].Length);
      }
      return result;
    }




    public long getAllocatedMemory()
    {
      long result = 0;
      if (data != null)
      {
        int channelIndex = 0;
        while (channelIndex < data.Length)
        {
          byte[] array = data[(int)channelIndex++];
          if (array != null)
          {
            result += array.Length;
          }
        }
      }
      return result;
    }




    public int getBitsPerPixel()
    {
      return numChannels * 8;
    }




    public byte getByteSample(RGBIndex channel, int x, int y)
    {
      /* advantage of the following approach: we don't check arguments 
         before we access the data (too costly); instead, we have the VM 
         throw an array index out of bounds exception and then determine 
         which of the arguments was wrong;
         that's better than checking before access all of the time => 
         the VM checks anyway
         we then throw a meaningful ArgumentException (in 
         checkPositionAndNumber)
         disadvantage: some erroneous arguments aren't noticed, example:
         width=100, height=20, x=100, y=0
         will not result in an error (because only 0..99 are valid for x) 
         but in the return of sample(0/1)
		   
          */
      try
      {
        return data[(int)channel][y * width + x];
      }
      catch (IndexOutOfRangeException aioobe)
      {
        checkPositionAndNumber(channel, x, y, 1, 1);
        return unchecked((byte)-1);
      }
    }




    public byte getByteSample(int x, int y)
    {
      return getByteSample(0, x, y);
    }




    public void getByteSamples(RGBIndex channel, int x, int y, int w, int h, byte[] dest, int destOffset)
    {
      checkPositionAndNumber(channel, x, y, w, h);
      byte[] src = data[(int)channel];
      try
      {
        int srcOffset = y * width + x;
        while (h-- > 0)
        {
          Array.Copy(src, srcOffset, dest, destOffset, w);
          srcOffset += width;
          destOffset += w;
        }
      }
      catch (IndexOutOfRangeException aioobe)
      {
      }
    }




    public int getHeight()
    {
      return height;
    }




    public int getMaxSample(RGBIndex channel)
    {
      return 255;
    }




    public int getNumChannels()
    {
      return numChannels;
    }




    public int getSample(int x, int y)
    {
      try
      {
        return firstChannel[y * width + x] & 0xff;
      }
      catch (IndexOutOfRangeException aioobe)
      {
        checkPositionAndNumber(0, x, y, 1, 1);
        return -1;
      }
    }




    public int getSample(RGBIndex channel, int x, int y)
    {
      try
      {
        return data[(int)channel][y * width + x] & 0xff;
      }
      catch (IndexOutOfRangeException aioobe)
      {
        checkPositionAndNumber(channel, x, y, 1, 1);
        return -1;
      }
    }




    public void getSamples(RGBIndex channel, int x, int y, int w, int h, int[] dest, int destOffs)
    {
      if (w < 1 || h < 1)
      {
        return;
      }
      byte[] src = data[(int)channel];
      int srcOffs = y * width + x;
      while (h-- != 0)
      {
        int loop = w;
        int from = srcOffs;
        while (loop-- != 0)
        {
          dest[destOffs++] = src[from++] & 0xff;
        }
        srcOffs += width;
      }
    }




    public int getWidth()
    {
      return width;
    }




    public void putByteSample(RGBIndex channel, int x, int y, byte newValue)
    {
      checkPositionAndNumber(channel, x, y, 1, 1);
      try
      {
        data[(int)channel][y * width + x] = newValue;
      }
      catch (IndexOutOfRangeException aioobe)
      {
        checkPositionAndNumber(channel, x, y, 1, 1);
      }
    }




    public void putByteSample(int x, int y, byte newValue)
    {
      checkPositionAndNumber(0, x, y, 1, 1);
      try
      {
        firstChannel[y * width + x] = newValue;
      }
      catch (IndexOutOfRangeException aioobe)
      {
        checkPositionAndNumber(0, x, y, 1, 1);
      }
    }




    public void putByteSamples(RGBIndex channel, int x, int y, int w, int h, byte[] src, int srcOffset)
    {
      checkPositionAndNumber(channel, x, y, w, h);
      byte[] dest = data[(int)channel];
      int destOffset = y * width + x;
      while (h-- > 0)
      {
        Array.Copy(src, srcOffset, dest, destOffset, w);
        srcOffset += w;
        destOffset += width;
      }
    }



    public void putSamples(RGBIndex channel, int x, int y, int w, int h, int[] src, int srcOffs)
    {
      checkPositionAndNumber(channel, x, y, w, h);
      byte[] dest = data[(int)channel];
      int destOffs = y * width + x;
      while (h-- != 0)
      {
        int loop = w;
        int to = destOffs;
        while (loop-- != 0)
        {
          dest[to++] = (byte)src[srcOffs++];
        }
        destOffs += width;
      }
    }



    //public System.Type getImageType()
    //{
    //  return this.GetType();
    //}



    public void putSample(int x, int y, int newValue)
    {
      putByteSample(0, x, y, (byte)newValue);
    }

    public void putSample(RGBIndex channel, int x, int y, int newValue)
    {
      putByteSample(channel, x, y, (byte)newValue);
    }
    public ImageFileDirectory Ifd
    {
      get { return _ifd; }
    }
  }
}